﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Centido.Management.ConsoleManager
{
    /// <summary>
    ///   The management console.
    /// </summary>
    internal class ManagementConsole
    {
        /// <summary>
        ///   The command prompt.
        /// </summary>
        public const string CommandPrompt = ">";

        /// <summary>
        ///   The console command parameter symbol.
        /// </summary>
        public const string CommandParameterSymbol = "-";


        /// <summary>
        ///   The current console command execution context.
        /// </summary>
        private readonly CommandExecutionContext _executionContext
            = new CommandExecutionContext();

        /// <summary>
        ///   The current command set.
        /// </summary>
        private CommandSet _commandSet = new PreLoginCommandSet();


        /// <summary>
        ///   Gets or sets the current command set.
        /// </summary>
        public CommandSet CurrentCommandSet
        {
            get { return _commandSet; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException();

                _commandSet = value;
            }
        }


        /// <summary>
        ///   Runs the management console.
        /// </summary>
        public void Run()
        {
            // Showing the management console greeting
            Console.WriteLine(Messages.TheManagementConsoleGreeting);
            Console.WriteLine();

            // Setting the console strings limit
            Console.SetIn(new StreamReader(Console.OpenStandardInput(8192)));

            // The main command loop
            while (true)
            {
                DisplayCommandPrompt();

                var commandStr = Console.ReadLine();

                if (!string.IsNullOrEmpty(commandStr) && commandStr.Trim().Length != 0)
                    ParseAndExecuteCommand(commandStr);
            }
        }


        /// <summary>
        ///   Displays the command propmpt.
        /// </summary>
        private void DisplayCommandPrompt()
        {
            // If there is a connection to a domain
            if (_executionContext.CurrentDomain != null)
            {
                // If the schema was not specified
                if (string.IsNullOrEmpty(_executionContext.CurrentDomain.DbSchemaName))
                    // Displaying the db name in the command propmpt
                    Console.Write("{0}.{1}{2}",
                        _executionContext.CurrentDomain.DbName,
                        _executionContext.CurrentDomain.CurrentUser.Name,
                        CommandPrompt);
                // If the schema was specified
                else
                    // Displaying the db name and schema in the command propmpt
                    Console.Write("{0}({1}).{2}{3}",
                        _executionContext.CurrentDomain.DbName,
                        _executionContext.CurrentDomain.DbSchemaName,
                        _executionContext.CurrentDomain.CurrentUser.Name,
                        CommandPrompt);
            }
            else
                Console.Write(CommandPrompt);
        }


        /// <summary>
        ///   Parses and executes the command specified in the string.
        /// </summary>
        /// <param name="commandStr">The command string.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="commandStr"/> is null.</exception>
        private void ParseAndExecuteCommand(string commandStr)
        {
            if (commandStr == null)
                throw new ArgumentNullException("commandStr");

            // Seaching for the specified command
            var command = GetCommand(commandStr);

            if (command == null)
            {
                Console.WriteLine(Messages.NoCommandWithTheSpecifiedName);
                return;
            }

            // Populating the command execution context
            PopulateCommandExecutionContext(commandStr);

            command.Execute(_executionContext);
        }


        /// <summary>
        ///   Gets an instance of the console command that is specified in the command string.
        /// </summary>
        /// <param name="commandStr">The command string.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="commandStr"/> is null.</exception>
        /// <returns>Returns an instance of the console command specified in the command string
        ///  if such a command can be foundl otherwise, returns null.</returns>
        private Command GetCommand(string commandStr)
        {
            if (commandStr == null)
                throw new ArgumentNullException("commandStr");

            var commandName = commandStr.Split(' ').First().Trim().ToLower();

            return _commandSet.GetCommandByName(commandName);
        }


        /// <summary>
        ///   Splits the specified command string into words.
        /// </summary>
        /// <param name="commandStr">The specified command string.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="commandStr"/> is null.</exception>
        /// <returns>Returns the list of words.</returns>
        private IList<string> SplitCommandStringToWords(string commandStr)
        {
            if (commandStr == null) 
                throw new ArgumentNullException("commandStr");

            
            var words = new List<string>();

            // The current word
            var currentWord = string.Empty;
            // The double quotes mode
            var doubleQuotesMode = false;

            foreach (var currentCh in commandStr)
            {
                // If encounting a space
                if (currentCh == ' ' || currentCh == '\t')
                {
                    // If it is not inside double quotes
                    if (!doubleQuotesMode)
                    {
                        // ... and there are some symbols in the current word
                        if (currentWord != string.Empty)
                        {
                            // Saving the current word
                            words.Add(currentWord);
                            currentWord = string.Empty;
                        }
                    }
                        // If it is inside double quotes
                    else
                        currentWord += currentCh;
                }

                    // If encounting the double quote
                else if (currentCh == '"')
                {
                    doubleQuotesMode = !doubleQuotesMode;
                }
                    // If this is a usual character
                else
                    currentWord += currentCh;
            }

            // If there are some symbols in the current word
            if (currentWord != string.Empty)
                // Saving the current word
                words.Add(currentWord);

            return words;
        }


        /// <summary>
        ///   Populates the command execution context.
        /// </summary>
        /// <param name="commandStr">The command string.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="commandStr"/> is null.</exception>
        private void PopulateCommandExecutionContext(string commandStr)
        {
            if (commandStr == null)
                throw new ArgumentNullException("commandStr");

            _executionContext.ManagementConsole = this;

            // Setting the command parameters
            _executionContext.ClearCommandParameters();

            var commandStrWords = SplitCommandStringToWords(commandStr);

            for (var i = 1; i < commandStrWords.Count(); i++)
            {
                var currentWord = commandStrWords[i].Trim();

                // If this not a command parameter
                if (!currentWord.StartsWith(CommandParameterSymbol)) continue;

                var parameterName = currentWord.TrimStart('-').ToLower();

                // If the parameter name is empty
                if (string.IsNullOrEmpty(parameterName)) continue;

                var parameter = new CommandParameter(parameterName);

                _executionContext.AddCommandParameter(parameter);

                // While the next word exists
                while (i + 1 < commandStrWords.Count()
                    // and it is not another command parameter
                    && !commandStrWords[i + 1].StartsWith(CommandParameterSymbol))
                {
                    // Then it is the parameter value
                    parameter.AddValue(commandStrWords[i + 1].Trim(' ', '"'));

                    i++;
                }
            }
        }
    }
}
